package com.huan;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 计算工作小时
 * 计算2个时间之间相差多少个 工作小时, 非工作小时之间的时间不进行计算。
 * 工作小时定义： 上午 8:30~11:30  下午 14:30~18:30
 * <p>
 * 需求：计算2个时间之间的 工作小时是多少个。 需要排除节假日（此处模拟排除）
 *
 * @author huan.fu
 * @date 2023/12/9 - 20:42
 */
public class CalcWorkHour {

    private static final Logger log = LoggerFactory.getLogger(CalcWorkHour.class);


    /**
     * 计算工作小时
     *
     * @param createBusiTime 业务创建时间
     * @param currentTime    当前时间
     * @param holidayType    模拟是否是节假日
     * @return 工作周
     */
    public static double calculateWorkingHours(LocalDateTime createBusiTime, LocalDateTime currentTime, int holidayType) {

        log.info("计算业务创建时间 createBusiTime:[{}] 和 当前时间currentTime:[{}] 节假日类型holidayType:[{}]", createBusiTime, currentTime, holidayType);

        // 定义上午和下午的工作时间范围
        LocalTime morningStart = LocalTime.of(8, 30);
        LocalTime morningEnd = LocalTime.of(11, 30);
        LocalTime afternoonStart = LocalTime.of(14, 30);
        LocalTime afternoonEnd = LocalTime.of(18, 30);

        // 根据 createBusiTime 和 currentTime 获取是节假日的时间
        List<String> holidays = holidays(holidayType);

        log.info("获取到的节假日为:[{}]", holidays);

        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        // 业务创建时间是否是节假日
        boolean createBusiTimeIsHoliday = holidays.contains(createBusiTime.format(dateFormatter));
        log.info("业务创建时间createBusiTime:[{}]是节假日:[{}]?", createBusiTime, createBusiTimeIsHoliday);
        // 当前时间是否是节假日
        boolean currentTimeIsHoliday = holidays.contains(currentTime.format(dateFormatter));
        log.info("当前时间currentTime:[{}]是节假日:[{}]?", currentTime, currentTimeIsHoliday);
        // 业务创建时间和当前时间是否是同一天
        boolean sameDay = Objects.equals(createBusiTime.format(dateFormatter), currentTime.format(dateFormatter));
        log.info("业务创建时间createBusiTime:[{}]和currentTime:[{}]是同一天:[{}]", createBusiTime, currentTime, sameDay);

        // 计算业务创建时间和当前时间隔了多少天
        long differDay = differDay(createBusiTime, currentTime);
        log.info("业务创建时间createBusiTime:[{}]和currentTime:[{}]是隔了:[{}]天", createBusiTime, currentTime, differDay);


        double workHours = 0;
        // 业务创建时间和当前时间是同一天
        if (sameDay) {
            log.info("处理业务创建时间和当前时间是同一天");
            // 当天不是节假日
            if (!createBusiTimeIsHoliday) {
                log.info("业务创建时间createBusiTime:[{}]不是节假日进行处理", createBusiTime);
                double tempWorkHours = calcWorkHour(morningStart, morningEnd, afternoonStart, afternoonEnd, createBusiTime.toLocalTime(), currentTime.toLocalTime());
                workHours += tempWorkHours;
                log.info("业务创建时间createBusiTime:[{}]和当前时间currentTime:[{}]计算出来的工作小时为:[{}]", createBusiTime, currentTime, tempWorkHours);
            } else {
                log.info("业务创建时间createBusiTime:[{}]是节假日不进行处理", createBusiTime);
            }
        } else {
            log.info("处理业务创建时间和当前时间不是同一天");
            // 业务创建时间不是节假日
            if (!createBusiTimeIsHoliday) {
                log.info("业务创建时间createBusiTime:[{}]不是节假日进行处理", createBusiTime);
                double tempWorkHours = calcWorkHour(morningStart, morningEnd, afternoonStart, afternoonEnd, createBusiTime.toLocalTime(), LocalTime.of(23, 59, 59));
                workHours += tempWorkHours;
                log.info("业务创建时间createBusiTime:[{}]和结束时间:[{}](跨天了)计算出来的工作小时为:[{}]", createBusiTime, LocalTime.of(23, 59, 59), tempWorkHours);
            } else {
                log.info("业务创建时间createBusiTime:[{}]是节假日不进行处理", createBusiTime);
            }

            // 当前时间不是节假日
            if (!currentTimeIsHoliday) {
                log.info("当前时间currentTime:[{}]不是节假日进行处理", currentTime);
                // 因为跨天了，那么业务的发生时间就取之前的业务发生时间和上午的工作开始时间的 最小时
                double tempWorkHours = calcWorkHour(morningStart, morningEnd, afternoonStart, afternoonEnd, min(createBusiTime.toLocalTime(), morningStart), currentTime.toLocalTime());
                workHours += tempWorkHours;
                log.info("当前时间createBusiTime:[{}]和当前时间currentTime:[{}]计算出来的工作小时为:[{}]", createBusiTime, currentTime, tempWorkHours);
            } else {
                log.info("业务创建时间currentTime:[{}]是节假日不进行处理", currentTime);
            }
        }

        // 需要补的工作天数 = 业务创建时间和当前时间相差的天数 - 节假日 - (如果开始时间和当天时间不是节假日，说明已经进行计算了工作小时，需要排除)

        long workDay = differDay;
        log.info("业务创建时间createBusiTime:[{}]和当前时间currentTime:[{}]之间相差了:[{}]天", createBusiTime, currentTime, differDay);
        log.info("排除:[{}]天的节假日", holidays.size());
        workDay -= holidays.size();
        if (!createBusiTimeIsHoliday) {
            log.info("业务发生时间:[{}]是工作日，工作小时已经进行计算了, 此处differDay需要减1", createBusiTime);
            workDay -= 1;
        }
        if (!sameDay) {
            if (!currentTimeIsHoliday) {
                log.info("当前时间:[{}]是工作日，工作小时已经进行计算了, 此处differDay需要减1", currentTime);
                workDay -= 1;
            }
        }

        log.info("最终计算出来的需要增加的工作日为:[{}]", workDay);

        workHours += Math.max(0, workDay) * (ChronoUnit.HOURS.between(morningStart, morningEnd) + ChronoUnit.HOURS.between(afternoonStart, afternoonEnd));

        log.info("最终计算出来业务创建时间:[{}]和当前时间:[{}]之间的工作小时为:[{}]", createBusiTime, currentTime, workHours);

        return workHours;
    }

    /**
     * 计算工作时间
     *
     * @param morningStart   上午工作开始时间
     * @param morningEnd     上午工作结束时间
     * @param afternoonStart 下午工作开始时间
     * @param afternoonEnd   下午工作结束时间
     * @param createBusiTime 可以理解为业务创建时间
     * @param endTime        可以理解为当前时间
     * @return 工作小时
     */
    private static double calcWorkHour(LocalTime morningStart, LocalTime morningEnd, LocalTime afternoonStart, LocalTime afternoonEnd,
                                       LocalTime createBusiTime, LocalTime endTime) {

        log.info("开始计算createBusiTime:[{}]的currentTime:[{}]之间的工作小时, 上午的工作小时区间:[{}]-[{}]，下午的工作小时区间为:[{}]-[{}]",
                createBusiTime, endTime, morningStart, morningEnd, afternoonStart, afternoonEnd);

        // 1、排除不计算工作时间的条件

        // 还未到早上的工作时间 - 工作时间为0
        if (endTime.isBefore(morningStart)) {
            log.info("当前时间还未到上午工作的开始时间,不进行工作小时的计算");
            return 0;
        }
        // 中午休息时间 - 工作时间为0
        if (createBusiTime.isAfter(morningEnd) && endTime.isBefore(afternoonStart)) {
            log.info("业务是在createBusiTime:[{}]上午的工作小时结束,当前时间:[{}]还未到下午工作小时:[{}]开始,不进行工作小时的计算",
                    createBusiTime, endTime, afternoonStart);
            return 0;
        }
        // 下午休息时间 - 工作时间为0
        if (createBusiTime.isAfter(afternoonEnd)) {
            log.info("业务的发生时间为createBusiTime:[{}]是在下午工作小时:[{}]结束之后发生，不进行工作小时的计算", createBusiTime, afternoonEnd);
            return 0;
        }

        double workHour = 0;

        // 计算上午的工作小时
        if (endTime.isAfter(morningStart)) {
            log.info("当前时间currentTime:[{}]是在早上的工作小时:[{}]之后,开始判断是否是上午发生的业务", endTime, morningStart);
            // 上午发生的业务 - 计算工作小时
            if (createBusiTime.isBefore(morningEnd)) {
                log.info("是上午发生的业务");
                double tempWorkHour = ChronoUnit.SECONDS.between(max(createBusiTime, morningStart), min(endTime, morningEnd)) / 3600.0;
                workHour += tempWorkHour;
                log.info("业务发生时间createBusiTime:[{}]在上午[{} - {}]产生了:[{}]个工作小时", createBusiTime, max(createBusiTime, morningStart), min(endTime, morningEnd), tempWorkHour);
            } else {
                log.info("不是上午发生的业务");
            }
        }

        if (endTime.isAfter(afternoonStart)) {
            log.info("当前时间currentTime:[{}]是在下午的工作小时:[{}]之后,开始判断是否有下午的工作小时产生", endTime, afternoonStart);
            // 上午发生的业务 时间来到了下午
            if (createBusiTime.isBefore(afternoonStart)) {
                log.info("业务发生时间createBusiTime:[{}]是上午发生的业务，但是需要计算下午的工作小时", createBusiTime);
                double tempWorkHour = ChronoUnit.SECONDS.between(afternoonStart, min(endTime, afternoonEnd)) / 3600.0;
                workHour += tempWorkHour;
                log.info("业务发生时间createBusiTime:[{}]在下午[{} - {}]产生了:[{}]个工作小时", createBusiTime, afternoonStart, min(endTime, afternoonEnd), tempWorkHour);
            } else {
                // 下午发生的业务
                log.info("业务发生时间createBusiTime:[{}]是下午产生的业务，开始计算工作小时", createBusiTime);
                double tempWorkHour = ChronoUnit.SECONDS.between(max(createBusiTime, afternoonStart), min(endTime, afternoonEnd)) / 3600.0;
                workHour += tempWorkHour;
                log.info("当天时间currentTime:[{}]是下午产生的业务，在[{} - {}]产生了:[{}]工作小时", endTime, max(createBusiTime, afternoonStart), min(endTime, afternoonEnd), tempWorkHour);
            }
        }
        return workHour;
    }


    /**
     * 根据类型 模拟返回 节假日
     */
    private static List<String> holidays(int type) {
        List<String> holidays = new ArrayList<>();
        switch (type) {
            case 1:
                holidays.add("2023-12-09");
                break;
            case 2:
                holidays.add("2023-12-10");
                break;
            case 3:
                holidays.add("2023-12-09");
                holidays.add("2023-12-10");
                break;
            default:
                break;
        }
        return holidays;
    }

    /**
     * 返回小的哪个时间
     */
    public static LocalTime min(LocalTime first, LocalTime second) {
        if (first.isBefore(second)) {
            return first;
        }
        return second;
    }

    /**
     * 返回大的时间
     */
    public static LocalTime max(LocalTime first, LocalTime second) {
        if (first.isBefore(second)) {
            return second;
        }
        return first;
    }

    /**
     * 判断2个时间之间相差几天，过了12点为1天
     *
     * @param start 开始时间
     * @param end   结束时间
     * @return 相差的天数
     */
    private static long differDay(LocalDateTime start, LocalDateTime end) {
        // 将两个时间调整到同一天的午夜12点
        LocalDate date1 = start.toLocalDate();
        LocalDate date2 = end.toLocalDate();
        LocalTime midnight = LocalTime.MIDNIGHT;
        LocalDateTime adjustedDateTime1 = LocalDateTime.of(date1, midnight);
        LocalDateTime adjustedDateTime2 = LocalDateTime.of(date2, midnight);

        // 计算天数差异（过了一天的晚上12点为1天）
        return ChronoUnit.DAYS.between(adjustedDateTime1, adjustedDateTime2) + 1;
    }

    public static void main(String[] args) {
        // 当天发生的业务，没有节假日产生
        // 上午发生的业务，但是当前还未到上午工作的时间(0)
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 7, 30), LocalDateTime.of(2023, 12, 8, 8, 30), 0));
        // 上午发生的业务，当天时间到了上午的工作之后之间(1)
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 7, 30), LocalDateTime.of(2023, 12, 8, 9, 30), 0));
        // 上午发生的业务，当天时间到了上午结束的工作时间之后，但是未到下午的工作小时之前(3)
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 7, 30), LocalDateTime.of(2023, 12, 8, 12, 30), 0));
        // 上午发生的业务，并且是上午工作时间发生的业务，当前时间还在上午的工作时间之内(2.83)
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 8, 35), LocalDateTime.of(2023, 12, 8, 11, 25), 0));
        // 下午发生的业务，但是当前还未到下午工作的时间(0)
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 12, 35), LocalDateTime.of(2023, 12, 8, 14, 0), 0));
        // 下午发生的业务，到了下午的工作时间，且下午的工作时间还未结束(0.5)
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 12, 35), LocalDateTime.of(2023, 12, 8, 15, 0), 0));
        // 下午发生的业务，到了下午的工作时间，且下午的工作时间结束了(4)
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 12, 35), LocalDateTime.of(2023, 12, 8, 19, 0), 0));
        // 下午发生的业务，并且是下午工作小时产生的业务(0.4167)
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 15, 35), LocalDateTime.of(2023, 12, 8, 16, 0), 0));
        // 上午产生的业务，下午还未结束(3.5)
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 7, 0), LocalDateTime.of(2023, 12, 8, 15, 0), 0));
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 7, 0), LocalDateTime.of(2023, 12, 8, 19, 0), 0));

        // 业务发生时间和当前时间都不是节假日(20.5)
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 9, 0), LocalDateTime.of(2023, 12, 10, 19, 0), 0));


        // 业务发生时间是节假日(0)
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 9, 8, 0), LocalDateTime.of(2023, 12, 9, 19, 0), 1));
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 9, 9, 0), LocalDateTime.of(2023, 12, 9, 19, 0), 1));
        // 7
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 9, 9, 0), LocalDateTime.of(2023, 12, 10, 19, 0), 1));
        // 7.5
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 9, 9, 0), LocalDateTime.of(2023, 12, 11, 9, 0), 1));

        // 业务发生时间是工作日，但是当前时间是节假日(6.5)
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 9, 0), LocalDateTime.of(2023, 12, 9, 19, 0), 1));
        // 13.5
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 9, 0), LocalDateTime.of(2023, 12, 10, 19, 0), 1));
        // 13.5
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 9, 0), LocalDateTime.of(2023, 12, 10, 19, 0), 2));
        // 6.5
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 9, 0), LocalDateTime.of(2023, 12, 10, 19, 0), 3));
        // 13.5
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 9, 0), LocalDateTime.of(2023, 12, 11, 19, 0), 3));
        // 11
        System.out.println(calculateWorkingHours(LocalDateTime.of(2023, 12, 8, 9, 0), LocalDateTime.of(2023, 12, 11, 16, 0), 3));
    }
}
